/**
 * SEF-ImageLazyload.js
 * Search engine friendly image lazy load.
 * 对搜索引擎友好的图片延迟加载库（img标签按标准写法即可，无需用临时属性代替src）
 * 畅捷通培训教育部 陈建宇 chenjye@chanjet.com
 * 
 * 开发背景
 * 市面上的图片延迟组件基本都是采用临时属性代替src值，在合适的时间再将临时值赋给src来实现，
 * 这样会导致页面对搜索引擎不友好，影响收录和在搜索结果中的排名。
 * 
 * 特性
 * 无代码侵入。仅将此js引入到页面中即可，程序自动处理。
 * 高性能。代码刻意避免重复操作，使用JS对象天然的Map特性提高执行效率，自实现高频调用节流器解决滚动事件频次高以及快速滚动图片显示问题。
 * 轻量级。仅一个js文件，实际代码小于100行。
 * 
 * 原理简介
 * 程序实现了：
 * 一个页面加载监听器：劫获所有图片，放到队列中。
 * 一个页面滚动监听器：当发生滚动时，将进入视口的图片放入待加载的队列中。
 * 一个图片加载器：每隔一定时间执行一次，将滚动监听器检查出的待加载图片显示出来。
 * 三个处理程序独立工作。
 * 程序利用DOMContentLoaded事件机制，在页面DOM加载完还未开始请求src资源时处理所有图片，把src转存到封装对象中。
 * 当发生页面上下滚动时，从待处理图片集合中获取已经处于视口内的图片放进待显示图片集合。图片健在器处理图片显示。
 * 滚动条事件处理时为了防止高频次触发实现了节流机制。
 * 加载图片程序实现了一个简单的并发锁(imageLoading变量)，同时只会处理一张图片的显示，只有当img的onload事件执行后才会解开锁，显示下一张图片。
 * 
 * 浏览器兼容：Chrome、Edge、Firefox、Safari
 * 若要兼容IE9/10/11需要做点改造：把DOMContentLoaded语句去掉，把代码直接放置到</body>之前直接执行。
 * 
 */

// 使用匿名函数闭包
(
    function() {
        // 加载真实图片前的默认图片（1像素，透明）
        // const defualtImage = '';
        
        // 加载真实图片前的默认图片（1像素，白色）
        const defaultImage = '';
        let waitShowImages = {};
        // 当前视口所有图片，滚动条滚动时，此数组会不停的变化，此数组中的图片会一张一张的显示（同步）。
        let currentViewportImageKeys = []; 
        let imageLoading = false;

        /**
         * 记录所有待加载的图片，加载完一个，删除一个。
         * 结构：{"image1": Object, "image2": Object, ...}
         * 为什么不用数组？
         * 因为此集合中的图片需要显示完成一个就删除一个，以便滚动条事件处理时避免重复处理
         * 通过object[key]的方式直接定位对象，不用循环遍历，删除时也可以直接使用delete object[key]
         * 如果用数组的话需要多执行一次循环，降低了执行效率
         */
        window.addEventListener('DOMContentLoaded', (event) => {
            // document.images比document.getElementsByTagName('img')效率高
            let images = document.images, image, imageObject;
            
            // 劫获图片，记录替换src
            for(let i=0; i<images.length; i++) {
                image = images[i];
                if (!image.src) return;
                imageObject = {
                    image,
                    src: image.src,
                    width: image.width || parseInt(image.style.width),
                    height: image.height || parseInt(image.style.height)
                };
                image.src = defaultImage;
                waitShowImages['image' + i] = imageObject;
            }
            
        });
        
        /**
         * 图片加载器
         */
        function loadImage() {
            if (imageLoading) {
                return false;
            }
            imageLoading = true;
            try {
                let firstKey = currentViewportImageKeys.shift(); // 摘掉第一个元素
                if (!firstKey) {
                    imageLoading = false;
                    return;
                }
                let imageObject = waitShowImages[firstKey];
                console.log(imageObject);
                imageObject.image.onload = event => {
                    delete waitShowImages[firstKey];
                    console.log('images ', Object.keys(waitShowImages).length);
                    imageLoading = false;
                };
                imageObject.image.src = imageObject.src;
            } catch(e) {
                imageLoading = false;
                console.log('ImageSeoLazyload error: ', e);
            }
        }
        
        /**
         * 图片位置检查器
         * 遍历还未显示过的图片，判断位置，把出现在视口的图片放进队列中，便于图片加载器函数处理显示。
         */
        function imagePositionChecker() {
            let scrollTop = document.body.scrollTop;
            let clientHeight = document.body.clientHeight;
            // 遍历当前待显示图片集合，找出出现在当前视口的图片放进currentViewportImages，执行此集合的显示，显示完一张便删除对应对象
            Object.keys(waitShowImages).forEach(key => {
                let imageObject = waitShowImages[key];
                if (imageObject.src && 
                    // 图片的顶边在视口上方附近且在下方附近
                    (imageObject.image.offsetTop < scrollTop + clientHeight + 100 && (scrollTop - imageObject.image.offsetTop) <= 400)
                    ||
                    // 或 图片完全在视口内
                    (imageObject.image.offsetTop > scrollTop && imageObject.image.offsetTop + imageObject.height < clientHeight)
                    ) {
                    currentViewportImageKeys.push(key);
                }
            });
        }
        
        /**
         * 页面滚动监听器，
         * 避免页面滚动时高频次调用图片检查，实现了一个简单的节流器，在滚动时每隔一段时间执行一次图片检查，
         * 这样快速滚动时也可以防止滚动过的图片都触发加载（看得到的才加载，太快划过的不加载）
         */
        let secondsOver = true;
        document.addEventListener('scroll', (event) => {
            // 未到一定时间则退出
            if (!secondsOver) {
                return;
            }
            secondsOver = false;
            // 一定时间后更新状态
            setTimeout(() => {secondsOver = true;}, 300);
            imagePositionChecker();
        });
        
        // 页面加载完成立即执行一次
        window.addEventListener('load', (event) => {
            imagePositionChecker();
            setInterval(loadImage, 200);
        });
        
    }
)();


